fix(claude-code): send MemoryItem.timestamp so the extractor can resolve relative dates - #2974
fix(claude-code): send MemoryItem.timestamp so the extractor can resolve relative dates#2974merlinr68 wants to merge 1 commit into
Conversation
…lve relative dates The retain hook never sent `timestamp`, so the extractor had no reference time for relative expressions. Transcripts saying "yesterday" or "last week" were stored with the phrase unresolved in the fact text (literally "When: Today (relative to conversation)"), which conveys nothing once the fact is recalled weeks later. The hook already computes the instant it needs — `template_vars["timestamp"]`, recorded as `retained_at` metadata — so this passes that same value through as the MemoryItem reference time. No new config, no behaviour change when a caller omits it. Measured against a self-hosted server, same transcript and mission, anchor the only variable: "last week we soldered the tamper pull-down" became a fact dated 2026-07-19, and "yesterday" became 2026-07-25. Hindsight's own best-practices guide lists this omission as an anti-pattern: "Missing `timestamp` on retain — disables temporal retrieval strategies".
handnewb
left a comment
There was a problem hiding this comment.
Nice fix! Without timestamps in MemoryItem, the extractor can't resolve relative dates like 'yesterday' or 'last week'. This is a quality-of-life improvement for recall accuracy.
|
Closing: superseded by Coding Agents, which carries timestamps at both levels. Each retained turn keeps its own absolute ISO timestamp in the JSONL transcript, and the retain itself is submitted with
|
Of ~1,400 facts accumulated in a real bank, exactly 1 carried a usable date. The retain hook never sends
MemoryItem.timestamp, so the extractor has no anchor: "yesterday" and "last week" are stored verbatim,occurred_startstays null, and the memory never reaches the Timeline. The hook already computes the value — it just never reaches the request body.Problem
The Claude Code retain hook never sends
MemoryItem.timestamp, so the extractor has no reference time to resolve relative expressions in a transcript.The result is facts that are stored but not usable later. A session saying "yesterday the sensors phantom-cascaded" is extracted as:
"Last week" relative to what? The anchor is exactly the information that was dropped. Once that fact is recalled weeks later it conveys nothing, and
occurred_startstays null so the memory never reaches the Timeline.This is listed as an anti-pattern in Hindsight's own best-practices guide:
and in the
timestampfield docs:Measurement
Same transcript, same
retain_mission, run throughdry-run-extracton a self-hosted server with the anchor as the only variable:timestamptimestampWhen: Last weekon 2026-07-19on 2026-07-25When: Today (relative to conversation)on 2026-07-26On a bank of ~1,400 facts accumulated without the anchor, exactly 1 carried an
occurred_start.Fix
The hook already computes the instant it needs.
run_retainbuildstemplate_vars["timestamp"](time.gmtime(), ISO 8601) and records it asretained_atmetadata — it just never reached the request body. This passes that same value through as the MemoryItem reference time.HindsightClient.retain(timestamp=None)sends notimestampkey at all, so the server keeps its existing ingestion-time fallback rather than receiving an explicit null.Tests
Two added to
TestRetainHook(203 pass, up from 201):test_retain_sends_timestamp_for_relative_date_resolution— asserts the posted item carries an ISO-8601timestampand that it matches theretained_atmetadata, so both describe the same instant.test_client_omits_timestamp_field_when_not_supplied— pins the omission path.Mutation-checked: deleting the
item["timestamp"] = timestampassignment fails the first test; restoring it passes.Scope note
This only supplies the anchor. Whether the extractor then populates
occurred_startis model-dependent — a local qwen3.6 resolved relative dates into the fact text but still leftoccurred_startnull, while a hosted model populated it 3/5 of the time on the same input. Sending the timestamp is a precondition for either, and improves the stored text regardless of which model is behind the extractor.